import {Controller, Get, Post, Body, Query, Record} from "../decorators/common";
import HttpErrorException from "../exceptions/HttpErrorException";
import HttpSuccessExpression from "../exceptions/HttpSuccessExpression";
import {userModel, authModel} from "../mongoose/model";

import {
  responseStatusMap as status,
  responseMessageMap,
  getFileData,
  StaticRouter,
  CurrentUser,
  OperationType, RequestMethod, RecordModule
} from "../utils";
import bcrypt from "bcryptjs";
import {IUserModelType, UserModelType} from "../mongoose/modelType/type";
import jwt from "jsonwebtoken";
import path from "path";
import {HydratedDocument} from "mongoose";
import {createRecordObj, RecordObj} from "../record";

const expiration = 60 * 60 * 24;
const message = responseMessageMap.userController;

@Controller("/api")
export default class UserController {

  @Post("/login")
  public async login(@Body account: string, @Body password: string) {
    const user = await userModel.findOne({
      account: account
    }) as IUserModelType;
    if (!user)
      return new HttpErrorException(status.failed, message.accountNotFound);

    const isPasswordValid = bcrypt.compareSync(password, user.password);
    if (!isPasswordValid)
      return new HttpErrorException(status.failed, message.infoError);

    const token = jwt.sign({
      id: user._id,
      ctime: Date.now()
    }, process.env.JWT_SECRET as string, {expiresIn: expiration});

    return new HttpSuccessExpression(status.success, message.loginSuccess, {
      token,
      expiration,
      user: Object.assign(user, {userId: user._id})
    });
  }

  @Get("/userList")
  public async getUserList() {
    const result = await userModel.find<IUserModelType>({account: {"$ne": ""}});
    if (result) {
      const userList = result!.map(user => {
        return {
          userId: user._id,
          account: user.account,
          username: user.username,
          userRole: user.userRole,
          createTime: user.createTime,
          phone: user.phone,
          email: user.email,
          status: user.status,
          description: user.description,
          gender: user.gender
        };
      });
      return new HttpSuccessExpression(status.success, message.getUserListSuccess, {userList});
    }
  }

  @Get("/userAuth")
  public async getUserAuthList(@Query() userId: any) {
    let user: IUserModelType;
    try {
      user = await userModel.findById(userId) as IUserModelType;
    } catch (e) {
      return new HttpErrorException(status.failed, message.userIdError);
    }

    if (!user) {
      return new HttpErrorException(status.failed, message.getUserAuthFailed);
    }

    const userRole = user.userRole!;
    const roleListSet = new Set();
    for (let i = 0; i < userRole.length; i++) {
      let list = [];
      let userAuth;
      if (userRole[i] === "admin" || userRole[i] === "common") {
        userAuth = await authModel.findOne({roleName: userRole[i]});
      } else {
        userAuth = await authModel.findOne({_id: userRole[i]});
      }
      if (!userAuth) {
        return;
      }
      list = userAuth.roleList;
      list?.forEach(i => {
        roleListSet.add(i);
      });
    }
    try {
      const roleListData = await getFileData(path.resolve(process.cwd(), "./src/router/router.json")) as string;
      let roleListObj = JSON.parse(roleListData).routerList as StaticRouter[];
      const roleList = Array.from(roleListSet).reduce((pre: any, next: any) => {
        const role = roleListObj.find(role => role.routeId === next);
        if (role) {
          pre.push(role);
        }
        return pre;
      }, []);
      return new HttpSuccessExpression(status.success, message.getUserAuthSuccess, {
        userId: user._id,
        account: user.account,
        username: user.username,
        roleList
      });
    } catch (e: any) {
      return new HttpErrorException(status.failed, e.message, e);
    }
  }

  @Post("/addUser")
  @Record(RecordModule.USER)
  public async addUser(
    @Body([{name: "account", required: true}, {name: "password", required: true}]) userInfo: IUserModelType,
    @Body([{name: "username", required: true}, {name: "userId", required: true}]) currentUser: CurrentUser
  ) {
    let user = await userModel.create<IUserModelType>(userInfo) as HydratedDocument<IUserModelType, any, any>;
    if (!user) {
      return new HttpErrorException(status.failed, message.addFailed);
    }
    const userId = user._doc._id;
    const userData = (delete user._doc._id && delete user._doc.password && delete user._doc.__v && user._doc);
    const record: RecordObj = {
      operator: currentUser.username,
      operatorId: currentUser.userId,
      operationType: OperationType.CREATE,
      requestMethod: RequestMethod.POST,
      newRecord: Object.assign({}, {userId}, userData)
    };
    return new HttpSuccessExpression(status.success, message.addSuccess, {
      user: Object.assign({}, {userId}, userData)
    }, createRecordObj(record));
  }

  @Post("/updateUser")
  @Record(RecordModule.USER)
  public async updateUser(
    @Body([{name: "userId", required: true}]) userInfo: IUserModelType,
    @Body([{name: "username", required: true}, {name: "userId", required: true}]) currentUser: CurrentUser
  ) {
    let _userInfo = userInfo;
    let oldUser = await userModel.findOneAndUpdate({_id: _userInfo.userId}, _userInfo, {lean: true}) as IUserModelType;
    if (!oldUser) {
      return new HttpErrorException(status.failed, message.updateFailed);
    }


    const userId = oldUser._id;
    const oldUserData = (delete (oldUser as any).password && delete (oldUser as any).__v && oldUser);

    const record: RecordObj = {
      operator: currentUser.username,
      operatorId: currentUser.userId,
      operationType: OperationType.UPDATE,
      requestMethod: RequestMethod.POST,
      oldRecord: Object.assign({}, {userId}, oldUserData),
      newRecord: Object.assign({}, {userId}, userInfo)
    };

    return new HttpSuccessExpression(status.success, message.updateSuccess, {
      user: Object.assign({}, userInfo)
    }, createRecordObj(record));
  }

  @Post("/deleteUser")
  @Record(RecordModule.USER)
  public async deleteUser(
    @Body() userId: string,
    @Body([{name: "username", required: true}, {name: "userId", required: true}]) currentUser: CurrentUser
  ) {
    let user = await userModel.findByIdAndDelete<IUserModelType>({_id: userId}, {new: true, lean: true});

    if (!user) {
      return new HttpErrorException(status.failed, message.deleteFailed);
    }

    const deleteUserId = user!._id;
    const userData = delete (user as any).__v && delete (user as any).password && user;

    const record: RecordObj = {
      operator: currentUser.username,
      operatorId: currentUser.userId,
      operationType: OperationType.DELETE,
      requestMethod: RequestMethod.POST,
      oldRecord: Object.assign({}, {userId: deleteUserId}, userData)
    };

    return new HttpSuccessExpression(status.success, message.deleteSuccess, {
      user: Object.assign({}, {userId: deleteUserId}, userData)
    }, createRecordObj(record));
  }

}
